package ar.edu.unlp.info.tesis.database.operator;

import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import ar.edu.unlp.info.tesis.database.BDUtils;
import ar.edu.unlp.info.tesis.database.exception.DatabaseException;
import ar.edu.unlp.info.tesis.domain.Entidad;



public class GenericControlerOperator {

	private String object = "obj", className;

	private Class clazz;

	private List<OrderParam> orderParams = new Vector<OrderParam>();

	private QueryOperator queryOperator = new QueryOperator();

	private boolean distinctEntity;
	
	private int maxResult=0,firstResult=0;
	

	public GenericControlerOperator(Class clase) {
		this.clazz = clase;
		this.className = clase.getName();
	}

	public GenericControlerOperator() {
		super();
	}

	/**
	 * Listado con los nombres de los atributos por los cuales se ordenaran, el
	 * ordenamiento de los registros respetara el orden de ingreso en el listado
	 */
	

	public List findByCriteria(Criterion... crit) {		
		Criteria criteria = BDUtils.getSession().createCriteria(this.getClazz());		
		for (int i = 0; i < crit.length; i++) {
			criteria.add(crit[i]);
		}
		return criteria.list();
	}
	

	public Entidad findByProperty(String property, Object value) {
		Criteria criteria = BDUtils.getSession().createCriteria(this.getClazz());	
		criteria.add(Restrictions.eq(property, value));
		return (Entidad) criteria.uniqueResult();
	}
	
	
	public void addOrderParam(String field, boolean orderASC) {
		if (field != null)
			this.orderParams.add(new OrderParam(orderASC, field));
	}

	public void addOrderParam(String field) {
		this.addOrderParam(field, true);
	}

	public void addOrderParams(List<String> fields, boolean orderASC) {
		for (String field : fields) {
			this.addOrderParam(field, orderASC);
		}

	}

	public Object getMax(String propertyName) throws HibernateException {
		Criteria cri = this.createCriteria();
		cri.setProjection(Projections.max(propertyName));
		cri.setFirstResult(0);
		return cri.uniqueResult();
	}

	protected Criteria createCriteria() {
		Criteria criteria;
		criteria = queryOperator.createQuery(this.clazz);
		if(this.getMaxResult()>0)criteria.setMaxResults(this.getMaxResult());
		if(this.getFirstResult()>0)criteria.setFirstResult(this.getFirstResult());

		if (this.distinctEntity)
			criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

		return criteria;
	}

	// para criteria de listados
	protected Criteria createListCriteria() throws HibernateException {
		Criteria criteria = this.createCriteria();
		this.addOrder(criteria);
		return criteria;
	}

	public Object getMin(String propertyName) throws HibernateException {
		Criteria cri = this.createCriteria();
		cri.setProjection(Projections.min(propertyName));
		cri.setFirstResult(0);
		return cri.uniqueResult();
	}

	public Object getCount(String propertyName) throws HibernateException {
		Criteria cri = this.createCriteria();
		cri.setProjection(Projections.count(propertyName));
		cri.setFirstResult(0);
		return cri.uniqueResult();
	}

	public Object getCountDistinct(String propertyName)
			throws HibernateException {
		Criteria cri = this.createCriteria();
		cri.list();
		cri.setProjection(Projections.countDistinct(propertyName));
		cri.setFirstResult(0);
		return cri.uniqueResult();
	}

	public Double getSum(String propertyName) throws HibernateException {
		Criteria cri = this.createCriteria();
		cri.setProjection(Projections.sum(propertyName));
		cri.setFirstResult(0);
		Double result = (Double) cri.uniqueResult();
		
		if (result == null)
			return 0.0;
		return result;
	}

	public Session getSession() {
		return BDUtils.getSession();
	}

	@SuppressWarnings("unchecked")
	public List<Entidad> getListByExample(Entidad exampleInstance,
			String... excludeProperty) {
		Criteria crit = this.getSession().createCriteria(clazz);
		Example example = Example.create(exampleInstance);
		example.excludeProperty("id");
		for (String exclude : excludeProperty) {
			example.excludeProperty(exclude);
		}
		crit.add(example);
		return crit.list();
	}

	/**
	 * metodo que retorna un persistent tomando como modelo otro, restando en la
	 * comparacion las propiedades del arreglo excludeProperty. Atencion, este
	 * metodo no acepta comparaciones con con propiedades que sean objetos.
	 * 
	 * @param exampleInstance:
	 *            modelo de comparacion
	 * @param excludeProperty:
	 *            las propiedades que quedan fuera de la busqueda
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Entidad getByExample(Entidad exampleInstance,
			String... excludeProperty) {
		Criteria crit = this.getSession().createCriteria(
				exampleInstance.getClass());
		Example example = Example.create(exampleInstance);
		Entidad p = null;
		example.excludeProperty("id");
		for (String exclude : excludeProperty) {
			example.excludeProperty(exclude);
		}
		crit.add(example);
		p = (Entidad) crit.uniqueResult();
		return p;
	}

	public Entidad getSameObject(Entidad exampleInstance) {
		Criteria crit = this.getSession().createCriteria(
				exampleInstance.getClass());
		Example example = Example.create(exampleInstance);
		Entidad p = null;
		crit.add(example);
		p = (Entidad) crit.uniqueResult();
		return p;
	}

	/**
	 * Genera una cosnsulta para obtension de un Objeto de una clase dada de la
	 * Base de Datos. La consulta es generada en base a los par�metros
	 * recibidos.
	 * 
	 * @param params
	 *            Son los parametros que tendra la consulta.
	 * @return Un Query obtenida en base a los parametros recibidos.
	 * @author Luis
	 */
	protected Query createGetQuery(String... params) {
		StringBuffer query = new StringBuffer("from " + className + " "
				+ this.object + " where ");
		for (String string : params) {
			query.append(this.object + "." + string + "=?");
			query.append(" and ");
		}
		int length = query.length();
		query.delete(length - 5, length);
		return this.getSession().createQuery(query.toString());
	}

	public Entidad getObjectByClassId(Class clase, long id) {
		return (Entidad) this.getSession().load(clase,
				new Long(id));
	}

	/**
	 * Genera una cosnsulta para obtener una lista de objetos de la Base de
	 * Datos.
	 * 
	 * @return La Query generada.
	 * @author Luis
	 */
	// protected Query createGetListQuery() {
	// return this.getSession().createQuery(
	// "from " + clase.getName() + this.getOrderString());
	// }
	/**
	 * Obtiene un unico resultado de la base de datos, si es que existe de
	 * acuerdo con lo criterion ingresados al dao.
	 * 
	 * @author luis
	 * @throws DatabaseException
	 * @throws HibernateException
	 */
	public Entidad uniqueResult() throws HibernateException {
		Criteria cri = this.createCriteria();
		cri.setFirstResult(0);
		return (Entidad) cri.uniqueResult();
	}
	
	public Object uniqueResultObject() throws HibernateException {
		Criteria cri = this.createCriteria();
		cri.setFirstResult(0);
		return  cri.uniqueResult();
	}

	/**
	 * Obtiene un unico resultado de la base de datos por el id del mismo.
	 * 
	 * @param id
	 *            Es el id del objeto en la Base de Datos.
	 * @return Un objeto de la Base de Datos.
	 * @author Luis
	 */
	public Entidad getById(long id) {
		Entidad persistent = null;
		persistent = (Entidad) this.createGetQuery("id").setLong(0, id).uniqueResult();
		return persistent;
	}
	
	public Entidad getByStringId(String id) {
		Entidad persistent = null;
		persistent = (Entidad) this.createGetQuery("id").setString(0, id).uniqueResult();
		return persistent;
	}

	/**
	 * @throws DatabaseException
	 * @throws HibernateException
	 * 
	 */
	@SuppressWarnings("unchecked")
	public List<Entidad> getList() throws HibernateException {
		Criteria cri = this.createListCriteria();
		return cri.list();
	}

	public long getSize() throws HibernateException {
		Criteria cri = this.createCriteria();
		cri.setProjection(Projections.rowCount());
		cri.setFirstResult(0);
		return (Long) cri.uniqueResult();

	}


	
	/**
	 * Guarda un objeto en la base de datos. El objeto debe ser instancia de una
	 * clase persistente. NOTA: este metodo debe llamarse dentro de una
	 * transaccion abierta.
	 * 
	 * @author Luis
	 */
	public void saveOrUpdate(Object obj) {
		this.getSession().saveOrUpdate(obj);
	}

	/**
	 * Elimina un objeto de l abase de Datos. NOTA: no abre una transaccion,
	 * debe hacerlo el metodo que lo llama.
	 * 
	 * @param obj
	 *            Es el objeto que eliminara de la Base de Datos.
	 * @author Luis
	 */
	public void delete(Object obj) {
		this.getSession().delete(obj);
		this.getSession().flush();
	}

	protected String getOrderString() {
		String order = " Order by ";
		if (this.getOrderParams() != null && this.getOrderParams().size() != 0) {
			Iterator<OrderParam> it = this.getOrderParams().iterator();

			while (it.hasNext()) {
				OrderParam op = (OrderParam) it.next();
				order = order + op.getField() + op.getKeyHqlString() + " , ";
			}
			order = order.substring(0, order.length() - 2);

		} else
			order = "";
		return order;
	}

	@SuppressWarnings("unchecked")
	public void setOrderParams(List<OrderParam> orderParams) {
		for (OrderParam param : orderParams) {
			param.setField(param.getField().trim());
		}
	}

	/**
	 * Retorna ua cantidad de elementos de la Base de Datos. Los elementos
	 * buscados comenzaran en la posicion inicial recibida por parametro, asi
	 * como tambien ser� especificada la cantidad de los mismos.
	 * 
	 * @param init
	 *            Es la posicion inicial de los elementos buscados en la Base de
	 *            Datos.
	 * @param lenght
	 *            Es la longitud de la lista resultante.
	 * @return Una lista de elementos de la Base de Datos.
	 * @author Luis
	 * @throws DatabaseException
	 * @throws HibernateException
	 */
	public List next(int init, int lenght) throws HibernateException {
		this.setMaxResult(lenght);
		this.setFirstResult(init);
		Criteria cri = this.createListCriteria();
		return cri.list();
	}

	/**
	 * para una consulta por criteria le brinda un orden si es q existen
	 * parametros de ordenamiento en el dao
	 * 
	 * @param criteria
	 *            Es la consulta por Criteria a ordenar.
	 * @author Luis
	 */
	public void addOrder(Criteria criteria) {
		try {
			for (OrderParam param : this.getOrderParams()) {
				Order order = null;
				if (param.isAscendent())
					order = Order.asc(param.getField());
				else
					order = Order.desc(param.getField());
				criteria.addOrder(order);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void setClassByString(String classObject) {
		try {
			this.clazz = Class.forName(classObject);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public String getClassName() {
		return className;
	}

	public void setClassName(String className) {
		this.className = className;
	}

	public List<OrderParam> getOrderParams() {
		return orderParams;
	}

	public QueryOperator getQueryOperator() {
		return queryOperator;
	}

	public void setQueryOperator(QueryOperator queryOperator) {
		this.queryOperator = queryOperator;
	}

	public void setDistinctEntity(boolean distinct) {
		distinctEntity = distinct;

	}

	public void resetDAO() {
		orderParams = new Vector<OrderParam>();
		queryOperator = new QueryOperator();
		distinctEntity = false;
	}

	public void resetOrderParams() {
		orderParams = new Vector<OrderParam>();
	}

	public Class getClazz() {
		return clazz;
	}

	public void setClazz(Class clase) {
		this.clazz = clase;
	}

	public int getFirstResult() {
		return firstResult;
	}

	public void setFirstResult(int firstResult) {
		this.firstResult = firstResult;
	}

	public int getMaxResult() {
		return maxResult;
	}

	public void setMaxResult(int maxResult) {
		this.maxResult = maxResult;
	}



}
